home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / hplip / scan.py < prev    next >
Text File  |  2009-10-09  |  45KB  |  1,175 lines

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  19. #
  20. # Author: Don Welch
  21. #
  22.  
  23. from __future__ import division
  24.  
  25. __version__ = '2.2'
  26. __mod__ = 'hp-scan'
  27. __title__ = 'Scan Utility'
  28. __doc__ = "SANE-based scan utility for HPLIP supported all-in-one/mfp devices (EXPERIMENTAL)."
  29.  
  30. # Std Lib
  31. import sys
  32. import os
  33. import os.path
  34. import getopt
  35. import signal
  36. import time
  37. import socket
  38. import operator
  39.  
  40. # Local
  41. from base.g import *
  42. from base import tui, device, module, utils
  43. from prnt import cups
  44.  
  45.  
  46. username = prop.username
  47. res = 300
  48. scan_mode = 'color'
  49. tlx = None
  50. tly = None
  51. brx = None
  52. bry = None
  53. units = "mm"
  54. output = ''
  55. dest = []
  56. email_from = ''
  57. email_to = []
  58. email_subject = 'hp-scan from %s' % socket.gethostname()
  59. email_note = ''
  60. fax = ''
  61. resize = 100
  62. contrast = 0
  63. brightness = 0
  64. #printer = ''
  65. page_size = ''
  66. size_desc = ''
  67. page_units = 'mm'
  68. valid_res = (75, 150, 300, 600, 1200, 2400, 4800)
  69. default_res = 300
  70. scanner_compression = 'JPEG'
  71. adf = False
  72.  
  73. PAGE_SIZES = { # in mm
  74.     '5x7' : (127, 178, "5x7 photo", 'in'),
  75.     '4x6' : (102, 152, "4x6 photo", 'in'),
  76.     '3x5' : (76, 127, "3x5 index card", 'in'),
  77.     'a2_env' : (111, 146, "A2 Envelope", 'in'),
  78.     'a3' : (297, 420, "A3", 'mm'),
  79.     "a4" : (210, 297, "A4", 'mm'),
  80.     "a5" : (148, 210, "A5", 'mm'),
  81.     "a6" : (105, 148, "A6", 'mm'),
  82.     "b4" : (257, 364, "B4", 'mm'),
  83.     "b5" : (182, 257, "B5", 'mm'),
  84.     "c6_env" : (114, 162, "C6 Envelope", 'in'),
  85.     "dl_env" : (110, 220, "DL Envelope", 'in'),
  86.     "exec" : (184, 267, "Executive", 'in'),
  87.     "flsa" : (216, 330, "Flsa", 'mm'),
  88.     "higaki" : (100, 148, "Hagaki", 'mm'),
  89.     "japan_env_3" : (120, 235, "Japanese Envelope #3", 'mm'),
  90.     "japan_env_4" : (90, 205, "Japanese Envelope #4", 'mm'),
  91.     "legal" : (215, 356, "Legal", 'in'),
  92.     "letter" : (215, 279, "Letter", 'in'),
  93.     "no_10_env" : (105, 241, "Number 10 Envelope", 'in'),
  94.     "oufufu-hagaki" : (148, 200, "Oufuku-Hagaki", 'mm'),
  95.     "photo" : (102, 152, "Photo", 'in'),
  96.     "super_b" : (330, 483, "Super B", 'in'),
  97.     }
  98.  
  99.  
  100. try:
  101.     viewer = ''
  102.     viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
  103.     for v in viewer_list:
  104.         vv = utils.which(v)
  105.         if vv:
  106.             viewer = os.path.join(vv, v)
  107.             break
  108.  
  109.  
  110.     editor = ''
  111.     editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
  112.     for e in editor_list:
  113.         ee = utils.which(e)
  114.         if ee:
  115.             editor = os.path.join(ee, e)
  116.             break
  117.  
  118.     pdf_viewer = ''
  119.     pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
  120.     for v in pdf_viewer_list:
  121.         vv = utils.which(v)
  122.         if vv:
  123.             pdf_viewer = os.path.join(vv, v)
  124.             break
  125.  
  126.     mod = module.Module(__mod__, __title__, __version__, __doc__, None,
  127.                         (NON_INTERACTIVE_MODE,))
  128.  
  129.     mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS,
  130.         extra_options=[utils.USAGE_SPACE,
  131.         ("[OPTIONS] (General)", "", "header", False),
  132.         ("Scan destinations:", "-s<dest_list> or --dest=<dest_list>", "option", False),
  133.         ("", "where <dest_list> is a comma separated list containing one or more of: 'file'\*, ", "option", False),
  134.         ("", "'viewer', 'editor', 'pdf', 'fax', or 'print'. Use only commas between values, no spaces.", "option", False),
  135.         ("Scan mode:", "-m<mode> or --mode=<mode>. Where <mode> is 'color'\*, 'gray' or 'lineart'.", "option", False),
  136.         ("Scanning resolution:", "-r<resolution_in_dpi> or --res=<resolution_in_dpi> or --resolution=<resolution_in_dpi>", "option", False),
  137.         ("", "where <resolution_in_dpi> is %s (300 is default)." % ', '.join([str(x) for x in valid_res]), "option", False),
  138.         ("Image resize:", "--resize=<scale_in_%> (min=1%, max=400%, default=100%)", "option", False),
  139.         ("Image contrast:", "--contrast=<contrast>", "option", False),
  140.         ("ADF mode (EXPERIMENTAL):", "--adf (Note, only PDF output is supported when using the ADF)", "option", False),
  141.         utils.USAGE_SPACE,
  142.         ("[OPTIONS] (Scan area)", "", "header", False),
  143.         ("Specify the units for area/box measurements:", "-t<units> or --units=<units>", "option", False),
  144.         ("", "where <units> is 'mm'\*, 'cm', 'in', 'px', or 'pt' ('mm' is default).", "option", False),
  145.         ("Scan area:", "-a<tlx>,<tly>,<brx>,<bry> or --area=<tlx>,<tly>,<brx>,<bry>", "option", False),
  146.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  147.         ("", "Units for tlx, tly, brx, and bry are specified by -t/--units (default is 'mm').", "option", False),
  148.         ("", "Use only commas between values, no spaces.", "option", False),
  149.         ("Scan box:", "--box=<tlx>,<tly>,<width>,<height>", "option", False),
  150.         ("", "tlx and tly coordinates are relative to the upper left corner of the scan area.", "option", False),
  151.         ("", "Units for tlx, tly, width, and height are specified by -t/--units (default is 'mm').", "option", False),
  152.         ("", "Use only commas between values, no spaces.", "option", False),
  153.         ("Top left x of the scan area:", "--tlx=<tlx>", "option", False),
  154.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  155.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  156.         ("Top left y of the scan area:", "--tly=<tly>", "option", False),
  157.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  158.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  159.         ("Bottom right x of the scan area:", "--brx=<brx>", "option", False),
  160.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  161.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  162.         ("Bottom right y   of the scan area:", "--bry=<bry>", "option", False),
  163.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  164.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  165.         ("Specify the scan area based on a paper size:", "--size=<paper size name>", "option", False),
  166.         ("", "where <paper size name> is one of: %s" % ', '.join(PAGE_SIZES.keys()), "option", False),
  167.         utils.USAGE_SPACE,
  168.         ("[OPTIONS] ('file' dest)", "", "header", False),
  169.         ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),
  170.         utils.USAGE_SPACE,
  171.         ("[OPTIONS] ('pdf' dest)", "", "header", False),
  172.         ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
  173.         utils.USAGE_SPACE,
  174.         ("[OPTIONS] ('viewer' dest)", "", "header", False),
  175.         ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
  176.         utils.USAGE_SPACE,
  177.         ("[OPTIONS] ('editor' dest)", "", "header", False),
  178.         ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
  179.         utils.USAGE_SPACE,
  180.         ("[OPTIONS] ('email' dest)", "", "header", False),
  181.         ("From: address for 'email' dest:", "--email-from=<email_from_address> (required for 'email' dest.)", "option", False),
  182.         ("To: address for 'email' dest:", "--email-to=<email__to_address> (required for 'email' dest.)", "option", False),
  183.         ("Email subject for 'email' dest:", '--email-subject="<subject>" or --subject="<subject>"', "option", False),
  184.         ("", 'Use double quotes (") around the subject if it contains space characters.', "option", False),
  185.         ("Note or message for the 'email' dest:", '--email-msg="<msg>" or --email-note="<note>"', "option", False),
  186.         ("", 'Use double quotes (") around the note/message if it contains space characters.', "option", False),
  187.         utils.USAGE_SPACE,
  188.         ("[OPTIONS] ('fax' dest)", "", "header", False),
  189.         ("Fax queue/printer:", "--fax=<fax_printer_name>", "option", False),
  190.         utils.USAGE_SPACE,
  191.         ("[OPTIONS] ('printer' dest)", "", "header", False),
  192.         ("Printer queue/printer:", "--printer=<printer_name>", "option", False),
  193.         utils.USAGE_SPACE,
  194.         ("[OPTIONS] (advanced)", "", "header", False),
  195.         ("Set the scanner compression mode:", "-x<mode> or --compression=<mode>, <mode>='raw', 'none' or 'jpeg' ('jpeg' is default) ('raw' and 'none' are equivalent)", "option", False),],
  196.         see_also_list=[])
  197.  
  198.     opts, device_uri, printer_name, mode, ui_toolkit, lang = \
  199.         mod.parseStdOpts('s:m:r:c:t:a:b:o:v:f:c:x:e:',
  200.                          ['dest=', 'mode=', 'res=', 'resolution=',
  201.                           'resize=', 'contrast=', 'adf', 'unit=',
  202.                           'units=', 'area=', 'box=', 'tlx=',
  203.                           'tly=', 'brx=', 'bry=', 'size=',
  204.                           'file=', 'output=', 'pdf=', 'viewer=',
  205.                           'email-from=', 'from=', 'email-to=',
  206.                           'to=', 'email-msg=', 'msg=', 'fax=',
  207.                           'printer=', 'compression=' , 'raw',
  208.                           'jpeg', 'color', 'lineart', 'colour',
  209.                           'bw', 'gray', 'grayscale', 'grey',
  210.                           'greyscale', 'email-subject=',
  211.                           'subject=', 'to=', 'from=', 'jpg',
  212.                           'grey-scale', 'gray-scale', 'about=',
  213.                           'editor='
  214.                          ])
  215.  
  216.  
  217.  
  218.     device_uri = mod.getDeviceUri(device_uri, printer_name,
  219.         back_end_filter=['hpaio'], filter={'scan-type': (operator.gt, 0)})
  220.  
  221.     for o, a in opts:
  222.         if o in ('-x', '--compression'):
  223.             a = a.strip().lower()
  224.  
  225.             if a in ('jpeg', 'jpg'):
  226.                 scanner_compression = 'JPEG'
  227.  
  228.             elif a in ('raw', 'none'):
  229.                 scanner_compression = 'None'
  230.  
  231.             else:
  232.                 log.error("Invalid compression value. Valid values are 'jpeg', 'raw', and 'none'.")
  233.                 log.error("Using default value of 'jpeg'.")
  234.                 scanner_compression = 'JPEG'
  235.  
  236.         elif o == 'raw':
  237.             scanner_compression = 'None'
  238.  
  239.         elif o == 'jpeg':
  240.             scanner_compression = 'JPEG'
  241.  
  242.         elif o in ('--color', '--colour'):
  243.             scan_mode = 'color'
  244.  
  245.         elif o in ('--lineart', '--line-art', '--bw'):
  246.             scan_mode = 'lineart'
  247.  
  248.         elif o in ('--gray', '--grayscale', '--gray-scale', '--grey', '--greyscale', '--grey-scale'):
  249.             scan_mode = 'gray'
  250.  
  251.         elif o in ('-m', '--mode'):
  252.             a = a.strip().lower()
  253.  
  254.             if a in ('color', 'colour'):
  255.                 scan_mode = 'color'
  256.  
  257.             elif a in ('lineart', 'bw', 'b&w'):
  258.                 scan_mode = 'lineart'
  259.  
  260.             elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
  261.                 scan_mode = 'gray'
  262.  
  263.             else:
  264.                 log.error("Invalid mode. Using default of 'color'.")
  265.                 log.error("Valid modes are 'color', 'lineart', or 'gray'.")
  266.                 scan_mode = 'color'
  267.  
  268.         elif o in ('--res', '--resolution', '-r'):
  269.             try:
  270.                 r = int(a.strip())
  271.             except ValueError:
  272.                 log.error("Invalid resolution. Using default of %s dpi." % default_res)
  273.                 log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
  274.                 res = default_res
  275.             else:
  276.                 if r in valid_res:
  277.                     res = r
  278.                 else:
  279.                     res = valid_res[0]
  280.                     min_dist = sys.maxint
  281.                     for x in valid_res:
  282.                         if abs(r-x) < min_dist:
  283.                             min_dist = abs(r-x)
  284.                             res = x
  285.  
  286.                     log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
  287.                     log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
  288.  
  289.         elif o in ('-t', '--units', '--unit'):
  290.             a = a.strip().lower()
  291.  
  292.             if a in ('in', 'inch', 'inches'):
  293.                 units = 'in'
  294.  
  295.             elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
  296.                 units = 'mm'
  297.  
  298.             elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
  299.                 units = 'cm'
  300.  
  301.             elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
  302.                 units = 'px'
  303.  
  304.             elif a in ('pt', 'point', 'points', 'pts'):
  305.                 units = 'pt'
  306.  
  307.             else:
  308.                 log.error("Invalid units. Using default of 'mm'.")
  309.                 units = 'mm'
  310.  
  311.         elif o == '--tlx':
  312.             a = a.strip().lower()
  313.             try:
  314.                 f = float(a)
  315.             except ValueError:
  316.                 log.error("Invalid value for tlx.")
  317.             else:
  318.                 tlx = f
  319.  
  320.         elif o == '--tly':
  321.             a = a.strip().lower()
  322.             try:
  323.                 f = float(a)
  324.             except ValueError:
  325.                 log.error("Invalid value for tly.")
  326.             else:
  327.                 tly = f
  328.  
  329.         elif o == '--brx':
  330.             a = a.strip().lower()
  331.             try:
  332.                 f = float(a)
  333.             except ValueError:
  334.                 log.error("Invalid value for brx.")
  335.             else:
  336.                 brx = f
  337.  
  338.         elif o == '--bry':
  339.             a = a.strip().lower()
  340.             try:
  341.                 f = float(a)
  342.             except ValueError:
  343.                 log.error("Invalid value for bry.")
  344.             else:
  345.                 bry = f
  346.  
  347.         elif o in ('-a', '--area'): # tlx, tly, brx, bry
  348.             a = a.strip().lower()
  349.             try:
  350.                 tlx, tly, brx, bry = a.split(',')[:4]
  351.             except ValueError:
  352.                 log.error("Invalid scan area. Using defaults.")
  353.             else:
  354.                 try:
  355.                     tlx = float(tlx)
  356.                 except ValueError:
  357.                     log.error("Invalid value for tlx. Using defaults.")
  358.                     tlx = None
  359.  
  360.                 try:
  361.                     tly = float(tly)
  362.                 except ValueError:
  363.                     log.error("Invalid value for tly. Using defaults.")
  364.                     tly = None
  365.  
  366.                 try:
  367.                     brx = float(brx)
  368.                 except ValueError:
  369.                     log.error("Invalid value for brx. Using defaults.")
  370.                     brx = None
  371.  
  372.                 try:
  373.                     bry = float(bry)
  374.                 except ValueError:
  375.                     log.error("Invalid value for bry. Using defaults.")
  376.                     bry = None
  377.  
  378.         elif o in ('-b', '--box'): # tlx, tly, w, h
  379.             a = a.strip().lower()
  380.             try:
  381.                 tlx, tly, width, height = a.split(',')[:4]
  382.             except ValueError:
  383.                 log.error("Invalid scan area. Using defaults.")
  384.             else:
  385.                 try:
  386.                     tlx = float(tlx)
  387.                 except ValueError:
  388.                     log.error("Invalid value for tlx. Using defaults.")
  389.                     tlx = None
  390.  
  391.                 try:
  392.                     tly = float(tly)
  393.                 except ValueError:
  394.                     log.error("Invalid value for tly. Using defaults.")
  395.                     tly = None
  396.  
  397.                 if tlx is not None:
  398.                     try:
  399.                         brx = float(width) + tlx
  400.                     except ValueError:
  401.                         log.error("Invalid value for width. Using defaults.")
  402.                         brx = None
  403.                 else:
  404.                     log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
  405.                     brx = None
  406.  
  407.                 if tly is not None:
  408.                     try:
  409.                         bry = float(height) + tly
  410.                     except ValueError:
  411.                         log.error("Invalid value for height. Using defaults.")
  412.                         bry = None
  413.                 else:
  414.                     log.error("Cannot calculate bry since tly is invalid. Using defaults.")
  415.                     bry = None
  416.  
  417.         elif o == '--size':
  418.             size = a.strip().lower()
  419.             if size in PAGE_SIZES:
  420.                 brx, bry, size_desc, page_units = PAGE_SIZES[size]
  421.                 tlx, tly = 0, 0
  422.                 page_size = size
  423.             else:
  424.                 log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(PAGE_SIZES.keys()))
  425.                 log.error("Using defaults.")
  426.  
  427.         elif o in ('-o', '--output', '-f', '--file'):
  428.             output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))
  429.  
  430.             try:
  431.                 ext = os.path.splitext(output)[1]
  432.             except IndexError:
  433.                 log.error("Invalid filename extension.")
  434.                 output = ''
  435.                 if 'file' in dest:
  436.                     dest.remove('file')
  437.             else:
  438.                 if ext.lower() not in ('.jpg', '.png'):
  439.                     log.error("Only JPG (.jpg) and PNG (.png) output files are supported.")
  440.                     output = ''
  441.                     if 'file' in dest:
  442.                         dest.remove('file')
  443.                 else:
  444.                     if os.path.exists(output):
  445.                         log.warn("Output file '%s' exists. File will be overwritten." % output)
  446.  
  447.                     if 'file' not in dest:
  448.                         dest.append('file')
  449.  
  450.         elif o in ('-s', '--dest', '--destination'):
  451.             a = a.strip().lower().split(',')
  452.             for aa in a:
  453.                 aa = aa.strip()
  454.                 if aa in ('file', 'fax', 'viewer', 'editor', 'printer', 'print', 'email', 'pdf') \
  455.                     and aa not in dest:
  456.                     if aa == 'print': aa = 'printer'
  457.                     dest.append(aa)
  458.  
  459.         elif o in ('-v', '--viewer'):
  460.             a = a.strip()
  461.             b = utils.which(a)
  462.             if not b:
  463.                 log.error("Viewer application not found.")
  464.             else:
  465.                 viewer = os.path.join(b, a)
  466.                 if 'viewer' not in dest:
  467.                     dest.append('viewer')
  468.  
  469.         elif o in ('--fax'):
  470.             if 'fax' not in dest:
  471.                 dest.append('fax')
  472.  
  473.         elif o in ('-e', '--editor'):
  474.             a = a.strip()
  475.             b = utils.which(a)
  476.             if not b:
  477.                 log.error("Editor application not found.")
  478.             else:
  479.                 editor = os.path.join(b, a)
  480.                 if 'editor' not in dest:
  481.                     dest.append('editor')
  482.  
  483.         elif o == '--pdf':
  484.             a = a.strip()
  485.             b = utils.which(a)
  486.             if not b:
  487.                 log.error("PDF viewer application not found.")
  488.             else:
  489.                 pdf_viewer = os.path.join(b, a)
  490.                 if 'pdf' not in dest:
  491.                     dest.append('pdf')
  492.  
  493.  
  494.         elif o in ('--email-to', '--to'):
  495.             email_to = a.split(',')
  496.             if 'email' not in dest:
  497.                 dest.append('email')
  498.  
  499.         elif o in ('--email-from', '--from'):
  500.             email_from = a
  501.             if 'email' not in dest:
  502.                 dest.append('email')
  503.  
  504.         elif o in ('--email-subject', '--subject', '--about'):
  505.             email_subject = a
  506.             if 'email' not in dest:
  507.                 dest.append('email')
  508.  
  509.         elif o in ('--email-note', '--email-msg', '--msg', '--message', '--note', '--notes'):
  510.             email_note = a
  511.             if 'email' not in dest:
  512.                 dest.append('email')
  513.  
  514.         elif o == '--resize':
  515.             a = a.replace("%", "")
  516.             try:
  517.                 resize = int(a)
  518.             except ValueError:
  519.                 resize = 100
  520.                 log.error("Invalid resize value. Using default of 100%.")
  521.  
  522.         elif o in ('-b', '--brightness'):
  523.             pass
  524.  
  525.         elif o in ('-c', '--contrast'):
  526.             try:
  527.                 contrast = int(a.strip())
  528.             except ValueError:
  529.                 log.error("Invalid contrast value. Using default of 100.")
  530.                 contrast = 100
  531.  
  532.         elif o == '--adf':
  533.             adf = True
  534.             output_type = 'pdf'
  535.  
  536.  
  537.     if printer_name is not None and \
  538.         device.getDeviceURIByPrinterName(printer_name) is not None and \
  539.         'printer' not in dest:
  540.  
  541.         dest.append('printer')
  542.  
  543.     if 'fax' in dest and 'file' not in dest:
  544.         log.error("Fax destination not implemented. Adding 'file' destination. Use resulting output file to fax.")
  545.         dest.append('file')
  546.  
  547.     if not dest:
  548.         log.warn("No destinations specified. Adding 'file' destination by default.")
  549.         dest.append('file')
  550.  
  551.     if 'file' in dest and not output:
  552.         log.warn("File destination enabled with no output file specified.")
  553.  
  554.         if adf:
  555.             log.info("Setting output format to PDF for ADF mode.")
  556.             output = utils.createSequencedFilename("hpscan", ".pdf")
  557.             output_type = 'pdf'
  558.         else:
  559.             if scan_mode == 'gray':
  560.                 log.info("Setting output format to PNG for greyscale mode.")
  561.                 output = utils.createSequencedFilename("hpscan", ".png")
  562.                 output_type = 'png'
  563.             else:
  564.                 log.info("Setting output format to JPEG for color/lineart mode.")
  565.                 output = utils.createSequencedFilename("hpscan", ".jpg")
  566.                 output_type = 'jpeg'
  567.  
  568.         log.warn("Defaulting to '%s'." % output)
  569.  
  570.     else:
  571.         try:
  572.             output_type = os.path.splitext(output)[1].lower()[1:]
  573.             if output_type == 'jpg':
  574.                 output_type = 'jpeg'
  575.         except IndexError:
  576.             output_type = ''
  577.  
  578.     if output_type and output_type not in ('jpeg', 'png', 'pdf'):
  579.         log.error("Invalid output file format. File formats must be 'jpeg', 'png', or 'pdf'.")
  580.         sys.exit(1)
  581.  
  582.     if adf and output_type and output_type != 'pdf':
  583.         log.error("ADF scans must be saved in PDF file format.")
  584.         sys.exit(1)
  585.  
  586.     if 'email' in dest and (not email_from or not email_to):
  587.         log.error("Email specified, but email to and/or email from address(es) were not specified.")
  588.         log.error("Disabling 'email' destination.")
  589.         dest.remove("email")
  590.  
  591.     if page_size:
  592.         units = 'mm'
  593.  
  594.     if units == 'in':
  595.         if tlx is not None: tlx = tlx * 25.4
  596.         if tly is not None: tly = tly * 25.4
  597.         if brx is not None: brx = brx * 25.4
  598.         if bry is not None: bry = bry * 25.4
  599.  
  600.     elif units == 'cm':
  601.         if tlx is not None: tlx = tlx * 10.0
  602.         if tly is not None: tly = tly * 10.0
  603.         if brx is not None: brx = brx * 10.0
  604.         if bry is not None: bry = bry * 10.0
  605.  
  606.     elif units == 'pt':
  607.         if tlx is not None: tlx = tlx * 0.3528
  608.         if tly is not None: tly = tly * 0.3528
  609.         if brx is not None: brx = brx * 0.3528
  610.         if bry is not None: bry = bry * 0.3528
  611.  
  612.     elif units == 'px':
  613.         log.warn("Units set to pixels. Using resolution of %ddpi for area calculations." % res)
  614.         if tlx is not None: tlx = tlx / res * 25.4
  615.         if tly is not None: tly = tly / res * 25.4
  616.         if brx is not None: brx = brx / res * 25.4
  617.         if bry is not None: bry = bry / res * 25.4
  618.  
  619.     if tlx is not None and brx is not None and tlx >= brx:
  620.         log.error("Invalid values for tlx (%d) and brx (%d) (tlx>=brx). Using defaults." % (tlx, brx))
  621.         tlx = brx = None
  622.  
  623.     if tly is not None and bry is not None and tly >= bry:
  624.         log.error("Invalid values for tly (%d) and bry (%d) (tly>=bry). Using defaults." % (tly, bry))
  625.         tly = bry = None
  626.  
  627.     if not prop.scan_build:
  628.         log.error("Scanning disabled in build. Exiting")
  629.         sys.exit(1)
  630.  
  631.     if mode == GUI_MODE:
  632.         log.error("GUI mode is not implemented yet. Please use -n. Refer to 'hp-scan -h' for help.")
  633.         sys.exit(1)
  634.  
  635.  
  636.     if 1:
  637.         #else: # NON_INTERACTIVE_MODE
  638.         import Queue
  639.         from scan import sane
  640.         import scanext
  641.         import cStringIO
  642.  
  643.         try:
  644.             import subprocess
  645.         except ImportError:
  646.             # Pre-2.4 Python
  647.             from base import subproc as subprocess
  648.  
  649.         try:
  650.             import Image
  651.         except ImportError:
  652.             log.error("%s requires the Python Imaging Library (PIL). Exiting." % __mod__)
  653.             sys.exit(1)
  654.  
  655.         sane.init()
  656.         devices = sane.getDevices()
  657.  
  658.         # Make sure SANE backend sees the device...
  659.         for d, mfg, mdl, t in devices:
  660.             if d == device_uri:
  661.                 break
  662.         else:
  663.             log.error("Unable to locate device %s using SANE backend hpaio:. Please check HPLIP installation." % device_uri)
  664.             sys.exit(1)
  665.  
  666.         log.info(log.bold("Using device %s" % device_uri))
  667.         log.info("Opening connection to device...")
  668.  
  669.         try:
  670.             device = sane.openDevice(device_uri)
  671.         except scanext.error, e:
  672.             sane.reportError(e)
  673.             sys.exit(1)
  674.  
  675.         tlx = device.getOptionObj('tl-x').limitAndSet(tlx)
  676.         tly = device.getOptionObj('tl-y').limitAndSet(tly)
  677.         brx = device.getOptionObj('br-x').limitAndSet(brx)
  678.         bry = device.getOptionObj('br-y').limitAndSet(bry)
  679.  
  680.         scan_area = (brx - tlx) * (bry - tly) # mm^2
  681.         scan_px = scan_area * res * res / 645.16 # res is in DPI
  682.  
  683.         if scan_mode == 'color':
  684.             scan_size = scan_px * 3 # 3 bytes/px
  685.         else:
  686.             scan_size = scan_px # 1 byte/px
  687.  
  688.         if scan_size > 52428800: # 50MB
  689.             if res > 600:
  690.                 log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
  691.             else:
  692.                 log.warn("The scan current parameters will cause very large files to be created.")
  693.  
  694.             log.warn("This can cause the scan to take a long time to complete and may cause your system to slow down.")
  695.             log.warn("Approx. number of bytes to read from scanner: %s" % utils.format_bytes(scan_size, True))
  696.  
  697.         res = device.getOptionObj('resolution').limitAndSet(res)
  698.  
  699.         device.setOption('compression', scanner_compression)
  700.  
  701.         if brx - tlx <= 0.0 or bry - tly <= 0.0:
  702.             log.error("Invalid scan area (width or height is negative).")
  703.             sys.exit(1)
  704.  
  705.         log.info("")
  706.         log.info("Resolution: %ddpi" % res)
  707.         log.info("Mode: %s" % scan_mode)
  708.         log.info("Compression: %s" % scanner_compression)
  709.         log.info("Scan area (mm):")
  710.         log.info("  Top left (x,y): (%fmm, %fmm)" % (tlx, tly))
  711.         log.info("  Bottom right (x,y): (%fmm, %fmm)" % (brx, bry))
  712.         log.info("  Width: %fmm" % (brx - tlx))
  713.         log.info("  Height: %fmm" % (bry - tly))
  714.  
  715.         if page_size:
  716.             units = page_units # for display purposes only
  717.             log.info("Page size: %s" % size_desc)
  718.             if units != 'mm':
  719.                 log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)
  720.  
  721.         if units == 'in':
  722.             log.info("Scan area (in):")
  723.             log.info("  Top left (x,y): (%fin, %fin)" % (tlx/25.4, tly/25.4))
  724.             log.info("  Bottom right (x,y): (%fin, %fin)" % (brx/25.4, bry/25.4))
  725.             log.info("  Width: %fin" % ((brx - tlx)/25.4))
  726.             log.info("  Height: %fin" % ((bry - tly)/25.4))
  727.  
  728.         elif units == 'cm':
  729.             log.info("Scan area (cm):")
  730.             log.info("  Top left (x,y): (%fcm, %fcm)" % (tlx/10.0, tly/10.0))
  731.             log.info("  Bottom right (x,y): (%fcm, %fcm)" % (brx/10.0, bry/10.0))
  732.             log.info("  Width: %fcm" % ((brx - tlx)/10.0))
  733.             log.info("  Height: %fcm" % ((bry - tly)/10.0))
  734.  
  735.         elif units == 'px':
  736.             log.info("Scan area (px @ %ddpi):" % res)
  737.             log.info("  Top left (x,y): (%fpx, %fpx)" % (tlx*res/25.4, tly*res/25.4))
  738.             log.info("  Bottom right (x,y): (%fpx, %fpx)" % (brx*res/25.4, bry*res/25.4))
  739.             log.info("  Width: %fpx" % ((brx - tlx)*res/25.4))
  740.             log.info("  Height: %fpx" % ((bry - tly)*res/25.4))
  741.  
  742.         elif units == 'pt':
  743.             log.info("Scan area (pt):")
  744.             log.info("  Top left (x,y): (%fpt, %fpt)" % (tlx/0.3528, tly/0.3528))
  745.             log.info("  Bottom right (x,y): (%fpt, %fpt)" % (brx/0.3528, bry/0.3528))
  746.             log.info("  Width: %fpt" % ((brx - tlx)/0.3528))
  747.             log.info("  Height: %fpt" % ((bry - tly)/0.3528))
  748.  
  749.         log.info("Destination(s): %s" % ', '.join(dest))
  750.  
  751.         if 'file' in dest:
  752.             log.info("Output file: %s" % output)
  753.  
  754.         update_queue = Queue.Queue()
  755.         event_queue = Queue.Queue()
  756.  
  757.         device.setOption("mode", scan_mode)
  758.         device.setOption("resolution", res)
  759.  
  760.         if adf:
  761.             try:
  762.                 device.setOption("source", "ADF")
  763.                 device.setOption("batch-scan", True)
  764.             except scanext.error:
  765.                 log.error("Failed to set ADF mode. Does this device support ADF? Disabling ADF mode.")
  766.                 adf = False
  767.  
  768.         if not adf:
  769.             try:
  770.                 #device.setOption("source", "Auto")
  771.                 device.setOption("batch-scan", False)
  772.             except scanext.error:
  773.                 log.debug("Error setting source or batch-scan option (this is probably OK).")
  774.  
  775.         log.info("\nWarming up...")
  776.  
  777.         no_docs = False
  778.         page = 1
  779.         adf_page_files = []
  780.         #adf_pages = []
  781.  
  782.         cleanup_spinner()
  783.         log.info("")
  784.  
  785.         try:
  786.             while True:
  787.                 if adf:
  788.                     log.info("\nPage %d: Scanning..." % page)
  789.                 else:
  790.                     log.info("\nScanning...")
  791.  
  792.                 bytes_read = 0
  793.  
  794.                 try:
  795.                     try:
  796.                         ok, expected_bytes, status = device.startScan("RGBA", update_queue, event_queue)
  797.                         # Note: On some scanners (Marvell) expected_bytes will be < 0 (if lines == -1)
  798.                         log.debug("expected_bytes = %d" % expected_bytes)
  799.                     except scanext.error, e:
  800.                         #log.error(e)
  801.                         sane.reportError(e)
  802.                         sys.exit(1)
  803.                     except KeyboardInterrupt:
  804.                         log.error("Aborted.")
  805.                         device.cancelScan()
  806.                         sys.exit(1)
  807.  
  808.                     if adf and status == scanext.SANE_STATUS_NO_DOCS:
  809.                         if page-1 == 0:
  810.                             log.error("No document(s). Please load documents and try again.")
  811.                             sys.exit(0)
  812.                         else:
  813.                             log.info("Out of documents. Scanned %d pages total." % (page-1))
  814.                             no_docs = True
  815.                             break
  816.  
  817.                     if expected_bytes > 0:
  818.                         if adf:
  819.                             log.info("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
  820.                         else:
  821.                             log.info("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
  822.  
  823.                     device.waitForScanActive()
  824.  
  825.                     pm = tui.ProgressMeter("Reading data:")
  826.  
  827.                     while device.isScanActive():
  828.                         while update_queue.qsize():
  829.                             try:
  830.                                 status, bytes_read = update_queue.get(0)
  831.  
  832.                                 #if log.get_level() >= log.LOG_LEVEL_INFO:
  833.                                 if not log.is_debug():
  834.                                     if expected_bytes > 0:
  835.                                         pm.update(int(100*bytes_read/expected_bytes),
  836.                                             utils.format_bytes(bytes_read))
  837.                                     else:
  838.                                         pm.update(0,
  839.                                             utils.format_bytes(bytes_read))
  840.  
  841.  
  842.     ##                            if status == scanext.SANE_STATUS_EOF:
  843.     ##                                log.debug("EOF")
  844.     ##                            elif status == scanext.SANE_STATUS_NO_DOCS:
  845.     ##                                log.debug("NO DOCS")
  846.     ##                                no_docs = True
  847.                                 #elif status != scanext.SANE_STATUS_GOOD:
  848.                                 if status != scanext.SANE_STATUS_GOOD:
  849.                                     sane.reportError(e)
  850.                                     #log.error("SANE error %d during readScan()" % status)
  851.                                     sys.exit(1)
  852.  
  853.                             except Queue.Empty:
  854.                                 break
  855.  
  856.  
  857.                         time.sleep(0.5)
  858.  
  859.                 except KeyboardInterrupt:
  860.                     log.error("Aborted.")
  861.                     device.cancelScan()
  862.                     sys.exit(1)
  863.  
  864.                 # Make sure queue is cleared out...
  865.                 while update_queue.qsize():
  866.                     status, bytes_read = update_queue.get(0)
  867.  
  868.                     if not log.is_debug():
  869.                         if expected_bytes > 0:
  870.                             pm.update(int(100*bytes_read/expected_bytes),
  871.                                 utils.format_bytes(bytes_read))
  872.                         else:
  873.                             pm.update(0,
  874.                                 utils.format_bytes(bytes_read))
  875.  
  876.     ##                if status == scanext.SANE_STATUS_EOF:
  877.     ##                    log.debug("EOF")
  878.     ##                elif status == scanext.SANE_STATUS_NO_DOCS:
  879.     ##                    log.debug("NO DOCS")
  880.     ##                    no_docs = True
  881.  
  882. #                    if status != scanext.SANE_STATUS_GOOD:
  883. #                        log.error("SANE error %d during queue clear" % status)
  884. #                        sys.exit(1)
  885.  
  886.                 log.info("")
  887.  
  888.                 if bytes_read:
  889.                     log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
  890.  
  891.                     buffer, format, format_name, pixels_per_line, \
  892.                         lines, depth, bytes_per_line, pad_bytes, total_read = device.getScan()
  893.  
  894.                     log.debug("PPL=%d lines=%d depth=%d BPL=%d pad=%d total=%d" %
  895.                         (pixels_per_line, lines, depth, bytes_per_line, pad_bytes, total_read))
  896.  
  897.                     if lines == -1:
  898.                         lines = int(total_read / bytes_per_line)
  899.  
  900.                     if scan_mode in ('color', 'gray'):
  901.                         try:
  902.                             im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
  903.                                 'raw', 'RGBA', 0, 1)
  904.                         except ValueError:
  905.                             log.error("Did not read enough data from scanner (I/O Error?)")
  906.                             sys.exit(1)
  907.  
  908.     ##                elif scan_mode == 'gray':
  909.     ##                    im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
  910.     ##                        'raw', 'RGBA', 0, 1).convert('P')
  911.  
  912.                     elif scan_mode == 'lineart':
  913.                         try:
  914.                             im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
  915.                                 'raw', 'RGBA', 0, 1).convert('L')
  916.                         except ValueError:
  917.                             log.error("Did not read enough data from scanner (I/O Error?)")
  918.                             sys.exit(1)
  919.  
  920.                     if adf:
  921.                         temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png")
  922.                         adf_page_files.append(temp_output)
  923.                         im.save(temp_output)
  924.                         log.debug("Saved page %d to file %s" % (page, temp_output))
  925.                         #adf_pages.append(im)
  926.                 else:
  927.                     log.error("No data read.")
  928.                     sys.exit(1)
  929.  
  930.                 if not adf or (adf and no_docs):
  931.                     break
  932.  
  933.                 page += 1
  934.  
  935.         finally:
  936.             log.info("Closing device.")
  937.             device.cancelScan()
  938.             #print "0"
  939.             #device.freeScan()
  940.             #sane.deInit()
  941.  
  942.         #if no_docs:
  943.         #    sys.exit(0)
  944.  
  945.         #print "1"
  946.         if adf:
  947.             try:
  948.                 from reportlab.pdfgen import canvas
  949.             except ImportError:
  950.                 log.error("PDF output requires ReportLab.")
  951.                 sys.exit(1)
  952.             #print "2"
  953.             #print canvas
  954.             #print canvas.Canvas
  955.  
  956.             tlx_max = device.getOptionObj('tl-x').constraint[1]
  957.             bry_max = device.getOptionObj('br-y').constraint[1]
  958.  
  959.             if not output:
  960.                 output = utils.createSequencedFilename("hpscan", ".pdf")
  961.  
  962.             c = canvas.Canvas(output, (tlx_max/0.3528, bry_max/0.3528))
  963.             #print c
  964.  
  965.             #for p in adf_page_files:
  966.             for p in adf_page_files:
  967.                 log.info("Processing page %s..." % p)
  968.  
  969.                 image = Image.open(p)
  970.                 #print image
  971.  
  972.                 try:
  973.                     c.drawInlineImage(image, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)),
  974.  
  975.                     #c.drawInlineImage(image, 0, bry/0.3528,
  976.                         width=None,height=None)
  977.                 except NameError:
  978.                     log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
  979.                     sys.exit(1)
  980.  
  981.                 c.showPage()
  982.  
  983.             log.info("Saving to file %s" % output)
  984.             c.save()
  985.             log.info("Viewing PDF file in %s" % pdf_viewer)
  986.             os.system("%s %s &" % (pdf_viewer, output))
  987.  
  988.  
  989.             sys.exit(0)
  990.  
  991.         if resize != 100:
  992.             if resize < 1 or resize > 400:
  993.                 log.error("Resize parameter is incorrect. Resize must be 0% < resize < 400%.")
  994.                 log.error("Using resize value of 100%.")
  995.             else:
  996.                 new_w = pixels_per_line * resize / 100
  997.                 new_h = lines * resize / 100
  998.                 log.info("Resizing from %dx%d to %dx%d..." % (pixels_per_line, lines, new_w, new_h))
  999.                 im = im.resize((new_w, new_h), Image.ANTIALIAS)
  1000.  
  1001.         file_saved = False
  1002.         if 'file' in dest:
  1003.             log.info("\nOutputting to destination 'file':")
  1004.             log.info("Saving to file %s" % output)
  1005.  
  1006.             try:
  1007.                 im.save(output)
  1008.             except IOError, e:
  1009.                 log.error("Error saving file: %s (I/O)" % e)
  1010.                 try:
  1011.                     os.remove(output)
  1012.                 except OSError:
  1013.                     pass
  1014.                 sys.exit(1)
  1015.             except ValueError, e:
  1016.                 log.error("Error saving file: %s (PIL)" % e)
  1017.                 try:
  1018.                     os.remove(output)
  1019.                 except OSError:
  1020.                     pass
  1021.                 sys.exit(1)
  1022.  
  1023.             file_saved = True
  1024.             dest.remove("file")
  1025.  
  1026.         temp_saved = False
  1027.         if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'printer' in dest) \
  1028.             and not file_saved:
  1029.  
  1030.             output_fd, output = utils.make_temp_file(suffix='.png')
  1031.             try:
  1032.                 im.save(output)
  1033.             except IOError, e:
  1034.                 log.error("Error saving temporary file: %s" % e)
  1035.  
  1036.                 try:
  1037.                     os.remove(output)
  1038.                 except OSError:
  1039.                     pass
  1040.  
  1041.                 sys.exit(1)
  1042.  
  1043.             os.close(output_fd)
  1044.             temp_saved = True
  1045.  
  1046.         for d in dest:
  1047.             log.info("\nSending to destination '%s':" % d)
  1048.  
  1049.             if d == 'fax':
  1050.                 log.error("fax: Not implemented yet.")
  1051.  
  1052.             elif d == 'pdf':
  1053.                 try:
  1054.                     from reportlab.pdfgen import canvas
  1055.                 except ImportError:
  1056.                     log.error("PDF output requires ReportLab.")
  1057.                     continue
  1058.  
  1059.                 tlx_max = device.getOptionObj('tl-x').constraint[1]
  1060.                 bry_max = device.getOptionObj('br-y').constraint[1]
  1061.  
  1062.                 pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
  1063.                 c = canvas.Canvas(pdf_output, (tlx_max/0.3528, bry_max/0.3528))
  1064.  
  1065.                 try:
  1066.                     c.drawInlineImage(im, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)),
  1067.                         width=None,height=None)
  1068.                 except NameError:
  1069.                     log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
  1070.                     continue
  1071.  
  1072.                 c.showPage()
  1073.                 log.info("Saving to file %s" % pdf_output)
  1074.                 c.save()
  1075.                 log.info("Viewing PDF file in %s" % pdf_viewer)
  1076.                 os.system("%s %s &" % (pdf_viewer, pdf_output))
  1077.  
  1078.             elif d == 'printer':
  1079.                 hp_print = utils.which("hp-print")
  1080.                 if hp_print:
  1081.                     cmd = 'hp-print %s &' % output
  1082.                 else:
  1083.                     cmd = "python ./print.py %s &" % output
  1084.  
  1085.                 os.system(cmd)
  1086.  
  1087.             elif d == 'email':
  1088.                 try:
  1089.                     from email.mime.image import MIMEImage
  1090.                     from email.mime.multipart import MIMEMultipart
  1091.                     from email.mime.text import MIMEText
  1092.                 except ImportError:
  1093.                     try:
  1094.                         from email.MIMEImage import MIMEImage
  1095.                         from email.MIMEMultipart import MIMEMultipart
  1096.                         from email.MIMEText import MIMEText
  1097.                     except ImportError:
  1098.                         log.error("hp-scan email destination requires Python 2.2+.")
  1099.                         continue
  1100.  
  1101.                 msg = MIMEMultipart()
  1102.                 msg['Subject'] = email_subject
  1103.                 msg['From'] = email_from
  1104.                 msg['To'] = ','.join(email_to)
  1105.                 msg.preamble = 'Scanned using hp-scan'
  1106.  
  1107.                 if email_note:
  1108.                     txt = MIMEText(email_note)
  1109.                     msg.attach(txt)
  1110.  
  1111.                 if file_saved:
  1112.                     txt = MIMEText("attached: %s: %dx%d %s PNG image." %
  1113.                         (os.path.basename(output), pixels_per_line, lines, scan_mode))
  1114.                 else:
  1115.                     txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))
  1116.  
  1117.                 msg.attach(txt)
  1118.  
  1119.                 fp = open(output, 'r')
  1120.                 img = MIMEImage(fp.read())
  1121.                 fp.close()
  1122.  
  1123.                 if file_saved:
  1124.                     img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))
  1125.  
  1126.                 msg.attach(img)
  1127.  
  1128.                 sendmail = utils.which("sendmail")
  1129.  
  1130.                 if sendmail:
  1131.                     sendmail = os.path.join(sendmail, 'sendmail')
  1132.                     cmd = [sendmail,'-t','-r',email_from]
  1133.  
  1134.                     log.debug(repr(cmd))
  1135.                     err = None
  1136.                     try:
  1137.                         sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  1138.                         std_out, std_err = sp.communicate(msg.as_string())
  1139.                         if std_err != '':
  1140.                             err = std_err
  1141.                     except OSError, e:
  1142.                         err = str(e)
  1143.                     cleanup_spinner()
  1144.  
  1145.                     if err:
  1146.                         log.error(repr(err))
  1147.  
  1148.                 else:
  1149.                     log.error("Mail send failed. 'sendmail' not found.")
  1150.  
  1151.             elif d == 'viewer':
  1152.                 if viewer:
  1153.                     log.info("Viewing file in %s" % viewer)
  1154.                     os.system("%s %s &" % (viewer, output))
  1155.                 else:
  1156.                     log.error("Viewer not found.")
  1157.  
  1158.             elif d == 'editor':
  1159.                 if editor:
  1160.                     log.info("Editing file in %s" % editor)
  1161.                     os.system("%s %s &" % (editor, output))
  1162.                 else:
  1163.                     log.error("Editor not found.")
  1164.  
  1165.         device.freeScan()
  1166.         sane.deInit()
  1167.  
  1168.  
  1169. except KeyboardInterrupt:
  1170.     log.error("User exit")
  1171.  
  1172. log.info("")
  1173. log.info("Done.")
  1174.  
  1175.